#pragma once

#include "Texture.h"
#include "Math2D.h"
#include "BSpline\BSpline.h"


//+------------------------------------------------------------------+
void FontColor(float r, float g, float b, float a = 1.0f);

//+------------------------------------------------------------------+
void DrawString(const std::wstring &str, float x = 0, float y = 0);

//+------------------------------------------------------------------+
void DrawStars(int filled, int half = 0, int total = 5, float x = 0, float y = 0, bool rightAligned = false);

//+------------------------------------------------------------------+
void DrawRect(int left, int top, int right, int bottom);

//+------------------------------------------------------------------+
inline
void DrawRect(const RECT &r)		{	DrawRect(r.left, r.top, r.right, r.bottom);	}

//+------------------------------------------------------------------+
void PixelColor(float r, float g, float b, float a = 1.0f);
void PixelColorReset();



//+==================================================================+
//	Interface for all objects, which are drawn every frame and
//	updated at least as often.
struct Drawable
{
//+------------------------------------------------------------------+
//	Must update the state of the object to time t.
	virtual void step(double t)
	{};

//+------------------------------------------------------------------+
//	Must draw the object.
	virtual void draw() const
	{};
};



//+==================================================================+
//	Base class for objects with position and speed.
//	To have translational scaling across all resoultions, independent
//	of texture scaling, e.g. to accomodate different aspect ratios,
//	we transform every point ourselves, using static variables.
struct Movable : public Drawable
{
// global size and translation scaling factors
//	-> subclasses should make use of these, or call our "draw"
	static float scale, sx, sy;
//----
	Vector2D<double> s, v;
	double	tOld;

//+------------------------------------------------------------------+
	Movable()																			: s(0,0), v(0,0), tOld(0){}
	Movable(const Vector2D<double> &_s)											: s(_s),  v(0,0), tOld(0){}
	Movable(const Vector2D<double> &_s, const Vector2D<double> &_v)	: s(_s),  v(_v),  tOld(0){}
	Movable(const Movable &copy)													: s(copy.s), v(copy.v), tOld(copy.tOld){}

//+------------------------------------------------------------------+
	void step(double t)
	{
		s += v * (t-tOld);
		tOld = t;
	}
//+------------------------------------------------------------------+
//	Subclasses might call this, to handle translation and scaling
//	but must safe and later restore the current modelview matrix.
	virtual void draw() const
	{
	//	translation to our global position and scaling.
		glTranslated(sx*s.x, sy*s.y, 0);
		glScalef(scale, scale, scale);
	}
};



//+==================================================================+
struct MovableTexture : public Movable
{
	const Texture *tex;

//+------------------------------------------------------------------+
	MovableTexture(const Texture *t = NULL) : tex(t){}

//+------------------------------------------------------------------+
	virtual void draw() const
	{
		tex->draw((float)(sx*s.x), (float)(sy*s.y), scale, true);
	}
};




//+==================================================================+
//	Will update its state in time, and be drawn when scheduled.
struct Animated : public Movable
{
	double	t0,								// start time
				duration,						// time length in seconds
				t;									// current time parameter if not idle, it's normalized between [0,1)
	float		size;								// size; can be used by child classes
	static const double IDLE;				// if(t==IDLE) the animation is over

//+------------------------------------------------------------------+
	Animated(double length = 1.0, double startTime = IDLE)
	:	t0(startTime), duration(length), t(IDLE), size(1.0f)
	{}

//+------------------------------------------------------------------+
	bool idle() const
	{
		return t == IDLE;
	}

//+------------------------------------------------------------------+
	virtual void step(double time)
	{
		t = time - t0;
		if(t >= 0 && t < duration)
			t /= duration;						// t is now between [0,1)
		else
			t = IDLE;

		Movable::step(time);
	}
};


//+==================================================================+
union Path
{
	struct{
		Point				end,					// The target position in "osu coordinates" (512x384)
							dir;					// direction vector of the button for approaching the end point (should be normalized)
		float				alpha1,				// amplitude        of the sin
							alpha2,				// amplitude        of the cos
							omega1,				//	angular velocity of the sin
							omega2;				//	angular velocity of the cos
	};

	__m128				m[2];					// SSE access
};

//+==================================================================+
struct FlyingButton : public Animated
{
	static const TiledTexture *tex;
	Path					path;					// the flying path
	wchar_t				id;					// [0,3] = [circle,cross,square,triangle]
	bool					chanceTime,			// is this button part of chance time?
							newCombo;			// does this button start a new combo?
	float					size,					// texture scaling
							time,					// target time in seconds
							d;						// traveled distance
	Point					pathPos;				// Last position, without the cos
	double				now;					//	last known time in seconds
	BSpline<float>		*tFunction;			// BSpline fitted function that maps the curves relative length[0,1] to its parameter t[0,1].
	Vector<Point,256>	trail;				// the points of the trail
	Vector<Point,256>	trailPerp;			// perpendicular points on the trail
	Vector<float,256>	trailTexS;			// texture coordinate s along the trail
	BYTE					trailIndex;			// the next to be written trail index - it will wrap around automatically when over 255.
	mutable int			hit;					// has the button been hit (is it gone)? If yes, how many frames?

//+------------------------------------------------------------------+
	FlyingButton() : Animated(0.0,1.0e9), d(0), pathPos(1.0e9,0), tFunction(NULL), trailIndex(0), hit(0)
	{
		memset(trailPerp,0,sizeof(trailPerp));
		memset(trailTexS,0,sizeof(trailTexS));
	}

//+------------------------------------------------------------------+
	~FlyingButton()
	{
		delete tFunction;
	}

//+------------------------------------------------------------------+
	virtual void step(double time);
//+------------------------------------------------------------------+
	virtual void draw() const;
//+------------------------------------------------------------------+
	void drawTrail() const;

//+------------------------------------------------------------------+
//	Comparison operator for sorting by time
	bool operator < (const FlyingButton &b) const
	{
		return this->time < b.time;
	}
};


//+==================================================================+
//	The tiles of the texture will be played as an animation
struct AnimatedTexture : public Animated
{
	const		TiledTexture *tex;			// texture with the frames (can be only one frame)
	wchar_t	frame;							// current frame, corresponding to t
	bool		centered;

//+------------------------------------------------------------------+
	AnimatedTexture(const TiledTexture *t = NULL, double duration = 1.0)
	:	Animated(duration), tex(t), frame(0), centered(true)
	{}

//+------------------------------------------------------------------+
	virtual void step(double time)
	{
		Animated::step(time);

		if(!idle())
			frame = wchar_t(t * tex->getCharCount());
	}

//+------------------------------------------------------------------+
	virtual void draw() const
	{
		if(!idle())
			tex->draw(frame, (float)(sx*s.x), (float)(sy*s.y), size*Movable::scale, centered);
	}
};


//+==================================================================+
struct AnimatedNumber : public Animated
{
	const			TiledTexture *tex;		// texture that includes the digits
	int			n;								// number to draw
	align::h		hAlign;
	align::v		vAlign;
	bool			sign;							// draw "+" on positive numbers

//+------------------------------------------------------------------+
	AnimatedNumber(const TiledTexture *t = NULL, double duration = 1.0, align::h h = align::right, align::v v = align::center, bool signedPositive = false)
	:	Animated(duration), tex(t), n(0), hAlign(h), vAlign(v), sign(signedPositive)
	{}

//+------------------------------------------------------------------+
	virtual void draw() const
	{
		if(!idle())
			tex->drawNumber(n, (float)(sx*s.x), (float)(sy*s.y), size*Movable::scale, hAlign, vAlign, sign);
	}
};



//+==================================================================+
//	These drawables can have variable alpha and size.
struct PopIn : public Drawable
{
	Animated	*ani;								// this will be drawn and updated
	Color		c;									// this color is used, with alpha being automatically adjusted
	bool		additiveColor;					// if this is true, the object and its color will be drawn in two passes.
	float		(*fAlpha)(float t);			// function to calculate alpha(t), for t in [0,1)
	float		(*fSize) (float t);			// function to calculate size (t), for t in [0,1)

	PopIn(Animated *animation = NULL, Color color = white)
	:	ani(animation), c(color), additiveColor(false), fAlpha(NULL), fSize(NULL)
	{}

//+------------------------------------------------------------------+
	virtual void step(double time)
	{
		if(ani)
			ani->step(time);
	}

//+------------------------------------------------------------------+
	virtual void draw() const
	{
		if(ani && !ani->idle())
		{
			static const float ByteToFloat(1.0f/255.0f);
		//----
		// size and alpha are functions of time
			float oldSize = ani->size;
			float alpha;
			if(fAlpha)	alpha			= fAlpha((float)ani->t);
			else			alpha			= float(sqr(ani->t-1.0));
			if(fSize)	ani->size  *= fSize((float)ani->t);

			if(additiveColor)
			{
			//----
			//	draw in white and change to additive blend function
				glColor4f(1.0f, 1.0f, 1.0f, alpha*ByteToFloat*c.A);
				ani->draw();

				glBlendFunc(GL_SRC_ALPHA, GL_ONE);
			}
		//----
		//	draw in color
			glColor4f(ByteToFloat*c.R, ByteToFloat*c.G, ByteToFloat*c.B, alpha*ByteToFloat*c.A);
			ani->draw();

			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

			ani->size = oldSize;				// restore base size
		}
	}
};



//+==================================================================+
struct PopInTexture : public PopIn
{
	AnimatedTexture a;

//+------------------------------------------------------------------+
	PopInTexture(const TiledTexture *t = NULL, double duration = 1.0, Color color = white)
	:	PopIn(&a, color), a(t, duration)
	{}

//+------------------------------------------------------------------+
	virtual void step(double time)
	{
	//----
	//	Keep the pointer updated, because of copying, etc.
	//	Alternatively, we could implement a proper copy constructor and assignment operator...
	//	This is safe enough, for now.
		ani = &a;
		PopIn::step(time);
	}
};


//+==================================================================+
struct PopInNumber : public PopIn
{
	AnimatedNumber n;

//+------------------------------------------------------------------+
	PopInNumber(const TiledTexture *t = NULL, double duration = 1.0, Color color = white)
	:	PopIn(&n, color), n(t, duration)
	{}

//+------------------------------------------------------------------+
	virtual void step(double time)
	{
	//----
	//	Keep the pointer updated, because of copying, etc.
	//	Alternatively, we could implement a proper copy constructor and assignment operator...
	//	This is safe enough, for now.
		ani = &n;
		PopIn::step(time);
	}
};


